package core

import (
	"sync"
	"time"
)

type IResult interface {

	// Wait will wait indefinitely for the Token to complete, ie the Publish
	// to be sent and confirmed receipt from the broker.
	Wait() (interface{}, bool)

	// WaitTimeout takes a time.Duration to wait for the flow associated with the
	// Token to complete, returns true if it returned before the timeout or
	// returns false if the timeout occurred. In the case of a timeout the Token
	// does not have an error set in case the caller wishes to wait again.
	WaitTimeout(time.Duration) (interface{}, bool)

	// Done returns a channel that is closed when the flow associated
	// with the Token completes. Clients should call Error after the
	// channel is closed to check if the flow completed successfully.
	//
	// Done is provided for use in select statements. Simple use cases may
	// use Wait or WaitTimeout.
	Done() <-chan struct{}

	Error() error
}

type Result struct {
	m        sync.RWMutex
	complete chan struct{}
	err      error
	value    interface{}
}

type ResultErrorSetter interface {
	setError(error)
}

type resultCompletor interface {
	Result
	ResultErrorSetter
	flowComplete()
}

func (b *Result) Error() error {
	b.m.RLock()
	defer b.m.RUnlock()
	return b.err
}

// Wait implements the Token Wait method.
func (b *Result) Wait() (interface{}, bool) {
	<-b.complete
	return b.value, true
}

// WaitTimeout implements the  WaitTimeout method.
func (b *Result) WaitTimeout(d time.Duration) (interface{}, bool) {
	timer := time.NewTimer(d)
	select {
	case <-b.complete:
		if !timer.Stop() {
			<-timer.C
		}
		return b.value, true
	case <-timer.C:
	}

	return b.value, false
}

func (b *Result) SetError(e error) {
	b.m.Lock()
	defer b.m.Unlock()
	b.err = e
	b.flowComplete()
}

func (b *Result) SetValue(value interface{}) {
	b.m.Lock()
	defer b.m.Unlock()
	b.value = value
	b.flowComplete()
}

func (b *Result) flowComplete() {
	select {
	case <-b.complete:
	default:
		close(b.complete)
	}
}

// Done implements the Token Done method.
func (b *Result) Done() <-chan struct{} {
	return b.complete
}

func NewResult() Result {
	return Result{complete: make(chan struct{})}
}
